#include <ngx_redis.h>

#define REDIS_DEBUG		0
#define REDIS_LINKS		20

#if REDIS_DEBUG
#define pr_err_info(fmt, args ...) \
	fprintf(stderr, fmt "\n", ##args)
#else
#define pr_err_info(fmt, args ...)
#endif

#define pr_pos_info() \
	pr_err_info("%s(%s:%d)", __FUNCTION__, __FILE__, __LINE__)

#define msleep(ms) \
	usleep(ms * 1000)

struct ngx_redis_pool gRedisPool = {
	.lock = PTHREAD_MUTEX_INITIALIZER,
	.cond = PTHREAD_COND_INITIALIZER,
};

void ngx_redis_client_init(struct ngx_redis_client *redis)
{
	pthread_mutex_init(&redis->lock, NULL);
	redis->context = NULL;
	redis->next = NULL;
	redis->host = REDIS_HOST;
	redis->port = REDIS_PORT;
}

void ngx_redis_client_deinit(struct ngx_redis_client *redis)
{
	pthread_mutex_destroy(&redis->lock);

	if (redis->context != NULL) {
		redisFree(redis->context);
	}
}

redisContext *ngx_redis_connect(struct ngx_redis_client *redis)
{
	redisContext *context;

	ngx_redis_client_lock(redis);

	context = redis->context;

	if (context == NULL) {
		pr_err_info("host = %s, port = %d", redis->host, redis->port);

		context = redisConnect(redis->host, redis->port);
		redis->context = context;
	}

	ngx_redis_client_unlock(redis);

	return context;
}

void ngx_redis_disconnect(struct ngx_redis_client *redis)
{
	redisContext *context;

	ngx_redis_client_lock(redis);

	context = redis->context;
	redis->context = NULL;

	if (context != NULL) {
		redisFree(context);
	}

	ngx_redis_client_unlock(redis);
}

redisReply *ngx_redis_execute(struct ngx_redis_client *redis, int argc, const char *argv[], const size_t argvl[])
{
	int i;

	for (i = 0; i < 10; i++) {
		redisContext *context = ngx_redis_connect(redis);
		if (context == NULL) {
			pr_err_info("connect");
		} else {
			void *reply = redisCommandArgv(context, argc, argv, argvl);
			if (reply != NULL) {
				return reply;
			}

			ngx_redis_disconnect(redis);
		}

		msleep(200);
	}

	return NULL;
}

struct ngx_redis_client *ngx_redis_get(struct ngx_redis_pool *pool)
{
	struct ngx_redis_client *redis;

	ngx_redis_pool_lock(pool);

	while (1) {
		redis = pool->head;
		if (redis != NULL) {
			pool->head = redis->next;
			break;
		}

		if (pool->links < REDIS_LINKS) {
			redis = malloc(sizeof(struct ngx_redis_client));
			if (redis != NULL) {
				ngx_redis_client_init(redis);
				break;
			}

			pr_err_info("malloc");
			ngx_redis_pool_timedwait(pool, 2000);
		} else {
			ngx_redis_pool_wait(pool);
		}
	}

	ngx_redis_pool_unlock(pool);

	return redis;
}

void ngx_redis_put(struct ngx_redis_pool *pool, struct ngx_redis_client *redis)
{
	ngx_redis_pool_lock(pool);

	if (pool->head == NULL) {
		ngx_redis_pool_notify(pool);
	}

	redis->next = pool->head;
	pool->head = redis;

	ngx_redis_pool_unlock(pool);
}
